'''
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with self work for additional information
 * regarding copyright ownership.  The ASF licenses self file
 * to you under the Apache License, Version 2.0 (the
 * "License") you may not use self file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http:#www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
'''
from base.exception.NumberFormatException import NumberFormatException
from base.java.StringBuffer import StringBuffer
from base.rdf.RDFReaderI import RDFReaderI
from base.rdf.impl.IStream import IStream

import re

#import java.io.*;
#import java.net.URL;
#import java.util.Hashtable;

#import org.apache.jena.atlas.logging.Log;
#import org.apache.jena.graph.GraphEvents;
#import org.apache.jena.rdf.model.*;
#import org.apache.jena.shared.JenaException;
#import org.apache.jena.shared.SyntaxError;
#import org.apache.jena.shared.UnknownPropertyException;
#import org.apache.jena.util.FileUtils;
#import org.slf4j.Logger;
#import org.slf4j.LoggerFactory;

'''
 * N-Triple Reader
'''


# extends obj,
class NTripleReader(RDFReaderI):
    # static final Logger log = LoggerFactory.getLogger(NTripleReader.class)

    ##    private Model model = None
    # param 
    # result Hashtable<String, Resource> anons = new Hashtable<>(self)
    anons = dict()

    ##    private IStream in = None
    ##    private boolean inErr = False
    ##    private int errCount = 0
    # private static final int
    inErr = False
    errCount = 0
    sbLength = 200

    # param 
    # result RDFErrorHandler errorHandler = new RDFDefaultErrorHandler(self)

    '''
     * Already with ": " at end for error messages.
    '''

    ##    private String base

    # param 
    def __init__(self):
        self.input = None

    # @Override
    # param Model model, InputStream in, String base
    # result void
    def read(self, model, reader, base):
        # N-Triples must be in ASCII, we permit UTF-8.
        # self.readInner(model, FileUtils.asUTF8(inp), base)
        self.readInner(model, reader, base)

    # @Override
    # param Model model, Reader reader, String base
    # result void
    def readInner(self, model, reader, base):
        self.model = model
        self.base = "" if base is None else (base + ": ")
        self.input = IStream(reader)
        self.readRDF()
        # if (errCount != 0):
        # raise SyntaxError("unknown")

    # @Override
    # param Model model, String url
    # result void
    # def read(self, model, url):
    #     try:
    #         read(
    #                 model,
    #                 InputStreamReader(((new URL(url))).openStream()),
    #                 url)
    #     catch (Exception e):
    #         raise JenaException(e)
    #     finally:
    #         if (errCount != 0):
    #             raise SyntaxError("unknown")

    # @Override
    # param String propName, obj propValue
    # result obj
    def setProperty(self, propName, propValue):
        # errorHandler.error(new UnknownPropertyException(propName))
        return None

    # @Override
    # param RDFErrorHandler errHandler
    # result RDFErrorHandler
    def setErrorHandler(self, errHandler):
        #RDFErrorHandler old = self.errorHandler
        #self.errorHandler = errHandler
        return None

    # param 
    # result void
    def readRDF(self):
        try:
            # model.notifyEvent(GraphEvents.startRead)
            self.unwrappedReadRDF()
        finally:
            pass
            # model.notifyEvent(GraphEvents.finishRead)

    # param 
    # result final void
    def unwrappedReadRDF(self):
        # Resource subject
        # Property predicate = None
        # RDFNode obj
        subject = None
        predicate = None
        obj = None

        while (not self.input.getEof()):
            while (not self.input.getEof()):
                inErr = False

                self.skipWhiteSpace()
                if (self.input.getEof()):
                    return

                subject = self.readResource()
                if (inErr):
                    break

                self.skipWhiteSpace()
                try:
                    # Resource 
                    r = self.readResource()
                    if (inErr):
                        break
                    predicate = self.model.createProperty(r.getURI())
                except Exception as e1:
                    pass
                    # errorHandler.fatalError(e1)

                if (inErr):
                    break

                self.skipWhiteSpace()
                obj = self.readNode()
                if (inErr):
                    break

                self.skipWhiteSpace()
                if (self.badEOF()):
                    break

                if (not self.expect(".")):
                    break

                try:
                    self.model.add(subject, predicate, obj)
                except Exception as e2:
                    pass
                    # errorHandler.fatalError(e2)

            if (self.inErr):
                self.errCount += 1
                while (not self.input.getEof() and self.input.readChar() != '\n'):
                    pass

    # param 
    # result Resource
    def readResource(self):
        # char 
        inChar = self.input.readChar()
        if (self.badEOF()):
            # Log.error(self, "**** Bad EOF")
            return None

        if (inChar == '_'):  # anon resource
            if (not self.expect(":")):
                return None
            # string
            name = self.readName()
            if (name is None):
                self.syntaxError("expected bNode label")
                return None

            return self.lookupResource(name)
        elif (inChar == '<'):  # uri
            # string
            uri = self.readURI()
            if (uri is None):
                inErr = True
                return None

            inChar = self.input.readChar()
            if (inChar != '>'):
                self.syntaxError("expected '>'")
                return None

            return self.model.createResource(uri)
        else:
            self.syntaxError("unexpected input")
            return None

    # param 
    # result RDFNode
    def readNode(self):
        self.skipWhiteSpace()
        nextChar = self.input.nextChar()
        # switch (self.input.nextChar()):
        if nextChar == '"':
            return self.readLiteral()
        elif nextChar == '<' or nextChar == '_':
            return self.readResource()
        else:
            self.syntaxError("unexpected input")
            return None

    # param 
    # result Literal
    def readLiteral(self):
        lit = StringBuffer('')

        if (not self.expect("\"")):
            return None

        while (True):
            # char
            inChar = self.input.readChar()
            if (self.badEOF()):
                return None
            if (inChar == '\\'):
                # char
                c = self.input.readChar()
                if (self.input.getEof()):
                    self.inErr = True
                    return None

                if (c == 'n'):
                    inChar = '\n'
                elif (c == 'r'):
                    inChar = '\r'
                elif (c == 't'):
                    inChar = '\t'
                elif (c == '\\' or c == '"'):
                    inChar = c
                elif (c == 'u'):
                    inChar = self.readUnicode4Escape()
                    if (self.inErr):
                        return None
                else:
                    self.syntaxError("illegal escape sequence '" + c + "'")
                    return None

            elif (inChar == '"'):
                # String
                lang = None
                if ('@' == self.input.nextChar()):
                    self.expect("@")
                    lang = self.readLang()
                elif ('-' == self.input.nextChar()):
                    self.expect("-")
                    self.deprecated("Language tags should be introduced with @ not -.")
                    lang = self.readLang()
                else:
                    lang = ""

                if ('^' == self.input.nextChar()):
                    # String
                    datatypeURI = None
                    if (not self.expect("^^<")):
                        self.syntaxError("ill-formed datatype")
                        return None

                    datatypeURI = self.readURI()
                    if (datatypeURI is None or not self.expect(">")):
                        return None
                    if (lang.length() > 0):
                        self.deprecated("Language tags are not permitted on typed literals.")

                    return self.model.createTypedLiteral(lit.toString(), datatypeURI)
                else:
                    return self.model.createLiteral(lit.toString(), lang)


            # Test for some raw characters
            elif (inChar == '\n' or inChar == '\r'):
                self.deprecated("Raw NL or CR not permitted in N-Triples data")
                return None

            lit = lit.append(inChar)

    # param 
    # result char
    def readUnicode4Escape(self):
        buf = [self.input.readChar(), self.input.readChar(), self.input.readChar(), self.input.readChar()]
        if (self.badEOF()):
            return 0

        bufStr = ''
        for s in buf:
            bufStr += s

        try:
            # return (char) Integer.parseInt(new String(buf), 16)
            return int(bufStr, 16)
        except NumberFormatException as e:
            self.syntaxError("bad unicode escape sequence")
            return 0

    # param String s
    # result void
    def deprecated(self, s):
        pass
        # errorHandler.warning(
        #         SyntaxError(
        #                 syntaxErrorMessage(
        #                         "Deprecation warning",
        #                         s,
        #                         self.input.getLinepos(),
        #                         self.input.getCharpos())))

    # param String s
    # result void
    def syntaxError(self, s):
        # errorHandler.error(
        #         SyntaxError(
        #                 syntaxErrorMessage(
        #                         "Syntax error",
        #                         s,
        #                         self.input.getLinepos(),
        #                         self.input.getCharpos())))
        self.inErr = True

    # param 
    # result String
    def readLang(self):
        lang = StringBuffer('')

        while (True):
            inChar = self.input.nextChar()
            # isWhiteSpace = re.match(r'\s', inChar)
            if (self.isWhiteSpace(inChar) or inChar == '.' or inChar == '^'):
                return lang.toString()
            lang = lang.append(self.input.readChar())

    def isWhiteSpace(self, char):
        return re.match(r'\s', char)

    # param 
    # result boolean
    def badEOF(self):
        if (self.input.getEof()):
            self.syntaxError("premature end of file")

        return self.inErr

    # param 
    # result String
    def readURI(self):
        uri = StringBuffer('')

        while (self.input.nextChar() != '>'):
            inChar = self.input.readChar()

            if (inChar == '\\'):
                self.expect("u")
                inChar = self.readUnicode4Escape()

            if (self.badEOF()):
                return None

            uri = uri.append(inChar)

        return uri.toString()

    # param 
    # result String
    def readName(self):
        name = StringBuffer('')

        nextChar = None
        # while (Character.isLetterOrDigit(nextChar = self.input.nextChar()) or '-' == nextChar):
        while self.isLetterOrDigitOrDash(self.input.nextChar()):
            name = name.append(self.input.readChar())
            if (self.badEOF()):
                return None

        return name.toString()

    def isLetterOrDigitOrDash(self, char):
        ret = ord(char)
        # 判断字母
        if ret >= 97 and ret <= 122:
            return True
        if ret >= 65 and ret <= 90:
            return True

        # 判断数字
        pattern = r'^\d$'
        if re.match(pattern, char):
            return True

        # 判断 -
        if char == '-':
            return True

        return False

    # param String str
    # result boolean
    def expect(self, str):
        for i in range(0, len(str)):
            want = str[i]

            if (self.badEOF()):
                return False

            inChar = self.input.readChar()

            if (inChar != want):
                self.syntaxError("expected \"" + str + "\"")
                return False

        return True

    # param 
    # result void
    def skipWhiteSpace(self):
        # while (Character.isWhitespace(self.input.nextChar()) or self.input.nextChar() == '#'):
        while (self.isWhiteSpace(self.input.nextChar()) or self.input.nextChar() == '#'):
            inChar = self.input.readChar()
            if (self.input.getEof()):
                return

            if (inChar == '#'):
                while (inChar != '\n'):
                    inChar = self.input.readChar()
                    if (self.input.getEof()):
                        return

    # param String name
    # result Resource
    def lookupResource(self, name):
        # Resource r
        r = self.anons.get(name)
        if (r is None):
            r = self.model.createResource()
            self.anons.put(name, r)

        return r

    # param String sort, String msg, int linepos, int charpos
    def syntaxErrorMessage(self, sort, msg, linepos, charpos):
        return self.base + sort + " at line " + linepos + " position " + charpos + ": " + msg
